/******************************************************************************
 * FUNCTIONS, PLUGINS, ETC
 ******************************************************************************/

/**
 * custom string format function (python-like)
 * use like "{0}".format("hallo") which results in
 * "hallo"
 */
String.prototype.format = function() {
  var args = arguments;
  return this.replace(/{(\d+)}/g, function(match, number) { 
    return typeof args[number] != 'undefined'
      ? args[number]
      : match
    ;
  });
};

/**
 * Plugin Maker jQuery plugin to generate plugins
 * out of classes
 * src: http://bitovi.com/blog/2010/10/writing-the-perfect-jquery-plugin.html
 */
$.pluginMaker = function(plugin) {
	// add the plugin function as a jQuery plugin
	$.fn[plugin.prototype.name] = function(options) {

		// get the arguments
		var args = $.makeArray(arguments), after = args.slice(1);

		return this.each(function() {
			// see if we have an instance
			var instance = $.data(this, plugin.prototype.name);
			if (instance) {
				// call a method on the instance
				if (typeof options == "string") {
					instance[options].apply(instance, after);
				} else if (instance.update) {
					// call update on the instance
					instance.update.apply(instance, args);
				}
			} else {
				// create the plugin
				var p = new plugin(this, options);
				$.data(this, plugin.prototype.name, p);
			}
		});
	};
};


/**
 * A Knowtemarks JSON client which supports: add/edit/delete actions on notes
 * and bookmarks. It can also retrieve notes or bookmarks by their given ID.
 * Further actions may follow.
 */
var KnowteClient = (function() {

	var urlDict = null;
	
	var validate = function(obj) { // TODO regex url and tags for allowed characters
		var testnote = function(obj) {
			return (obj.title != undefined && obj.title.length > 0
					&& obj.info != undefined && obj.tagString != undefined 
					&& obj.tagString.length > 0);
		};
		return testnote(obj);
	};
	
	var ajaxRequest = function(url, obj, event, callback) {
		if (event == null) event = jQuery.Event();
		$.post(url, obj, function(data, textStatus, jqXHR) {
			//console.log(data);
			event.item = obj;
			event.data = data;
			event.status = "success";
			event.message = textStatus;
			callback(event);
		});
			/*error: function(jqXHR, textStatus, errorThrown) {
				console.log(textStatus + ": " + errorThrown);
				var e = jQuery.Event(event);
				e.item = obj;
				e.status = "error";
				e.message = textStatus;
				callback(e);
			},
			timeout: function() {
				var e = jQuery.Event(event);
				e.status = "timeout";
				callback(e);
			}*/
	};
	
	/**
	 * Add an item
	 * @param type (bookmark|note)
	 * @param obj object representation of a note or a bookmark
	 * @throws Exception if JSON object is malformed
	 */
	var add = function(obj, callback, data) {
		if(!validate(obj)) {
			throw "Malformed JSON object";
		}
		event = jQuery.Event();
		event.inData = data;
		var url = urlDict.getItemUrl("add");
		ajaxRequest(url, obj, event, callback);
	};
	
	/**
	 * edit an item
	 * @param type (bookmark|note)
	 * @param obj object representation of a note or a bookmark
	 * @throws Exception if JSON object is malformed
	 */
	var edit = function(obj, callback, data) {
		if(!validate(obj) || obj.id === undefined) {
			throw "Malformed JSON object";
		}
		event = jQuery.Event();
		event.inData = data;
		var url = urlDict.getItemUrl("edit", obj.id);
		ajaxRequest(url, obj, event, callback);
	};
	
	var del = function(id, callback, data) {
		event = jQuery.Event();
		event.inData = data;
		var url = urlDict.getItemUrl("del", id);
		ajaxRequest(url, "", event, callback);
	};

	/**
	 * initialize with a URL dict instance to look up json api URLs
	 */
	var init = function init(urlD) {
		urlDict = urlD;
		
		return {
			add: add,
			edit: edit,
			del: del
		};
	};
	
	return { init: init };
})();

/**
 * A dictionary containing request URLs for JSON calls
 */
var UrlDict = (function() {
	var contextRoot = "";
	var instance = null;
	// default mapping
	var _mappings = {
			add:"/a/add",
			edit:"/a/edit/{id}",
			del:"/a/delete/{id}",
	};
	var setPath = function(name, value) {
		_mappings[name] = value;
	};
	var getItemUrl = function(action, id) {
		var res = _mappings[action];
		if(res != undefined) {
			return contextRoot + res.replace("{id}", id);
		} else {
			throw "No such or action " + action;
		}
	};
	
	function init(context) {
		contextRoot = context;
		return {
			setPath: setPath,
			getItemUrl: getItemUrl,
		};
	};
	
	// singleton
	return {
		getInstance: function(contextRoot) {
			if(!instance) {
				return instance = init(contextRoot);
			}
			return instance;
		}
	};
})();

/**
 * A cache for JTemplates template files.
 * The files are fetched from a remote source once there is a
 * cache miss.
 */
var TemplateCache = (function() {
	var instance = null;
	var contextRoot = "/";
	var _mappings = {
			itemDialog: "/static/templates/itemDialog.html",
			itemForm: "/static/templates/itemForm.html"
	};
	
	var _cacheVals = {};
	
	var getTemplate = function(name) {
		if(_mappings[name] == undefined || _mappings[name] == null) throw "No such template: " + name;
		
		if(_cacheVals[name] === undefined || _cacheVals[name] == null) {
			_cacheVals[name] = $.createTemplateURL(contextRoot + _mappings[name]);
		}
		return _cacheVals[name];
	};
	
	var setTemplate = function(name, path) {
		_mappings[name] = path;
	};
	
	function init(context) {
		contextRoot = context;
		return {
			getTemplate: getTemplate,
			setTemplate: setTemplate
		};
	}
	
	return {
		getInstance: function(contextRoot) {
			if(!instance) {
				instance = init(contextRoot);
			}
			return instance;
		}
	};
})();


/**
 * The multi purpose item form
 * It has an "add" and an "edit" mode
 * 
 * element: a container where the form should be
 * loaded in 
 * 
 * options:
 *   {
 *   	knowteClient: a client for the knowtemarks web app
 *   	temlateCache: to load the form template (getTemplate("itemForm") should return the HTML)
 *   }
 */
ItemForm = function(element, options) {
	if(element) {
		this.element = element;
		this.templateCache = options.templateCache;
		this.knowteClient = options.knowteClient;
		//this.options = $.extend({}, defaults, options);
		//this._defaults = defaults;
		this.init(element, options);
	}
};
$.extend(ItemForm.prototype, {
	name: "itemForm",
	init: function(element, options) {
		$(element).html("");
		var t = this.templateCache.getTemplate("itemForm");
		console.log(t);
		$(element).setTemplate(t);
		$(element).processTemplate({id:0});
		
		/* get form elements for easier access */
		this.idField = $("#itemForm0").find("input[name='id']");
		this.titleField = $("#itemForm0").find("input[name='title']");
		this.urlField = $("#itemForm0").find("input[name='url']");
		this.infoField = $("#itemForm0").find("textarea");
		this.tagsField = $("#itemForm0").find("input[name='tags']");
		this.submitBtn = $("#itemForm0").find("input[type='submit']");
		this.cancelBtn = $("#itemForm0").find(".cancelbtn");
		
		/* initially we are in "add" mode */
		this.addMode();
		this.submitBtn.on("click", {pluginInstance:this}, function(event) { event.preventDefault(); event.data.pluginInstance._submit(event.data.pluginInstance);});
		this.cancelBtn.on("click", {pluginInstance:this}, function(event) { event.preventDefault(); event.data.pluginInstance.addMode();});
	},
	
	addMode: function() {
		this.mode = "add";
		this.cancelBtn.hide();
		this.submitBtn.val("Add");
		
		this.idField.val("");
		this.titleField.val("");
		this.urlField.val("");
		this.infoField.html("");
		this.infoField.val("");
		this.tagsField.val("");
	},
	
	editMode: function(knowte) {
		this.mode = "edit";
		this.cancelBtn.show();
		this.submitBtn.val("Submit");
		
		this.idField.val(knowte.id);
		this.titleField.val(knowte.title);
		this.urlField.val(knowte.url);
		this.infoField.val(knowte.info);
		this.tagsField.val(knowte.tagString);
	},
	
	_submit: function(that) {
		/* get data from fields */
		var o = {
				id:that.idField.val(),
				info:that.infoField.val(),
				title:that.titleField.val(),
				url:that.urlField.val(),
				tagString:that.tagsField.val()
		};
		
		if(that.mode === "edit") {
			knowteClient.edit(o, that._editCallback, {knowte:o,that:that});
		} else if(that.mode === "add") {
			knowteClient.add(o, that._addCallback, {knowte:o,that:that});
		} else {
			alert("programming error: unknown state");
		}
	},
	
	_editCallback: function(event) {
		var that = event.inData.that;
		var k = event.inData.knowte;
		
		// update item view
		var e = $(".item[id$='" + k.id + "']");
		if (e != undefined) {
			e.find("input[name='itemid']").val(k.id);
			e.find(".item-title-text").html(k.title);
			e.find(".item-url-text").html(k.url);
			e.find(".item-content-text").html(k.info);
			
			var tpl = $.createTemplate("<a href=\"/t/{$T.name}\"><span class=\"label item-tag\">{$T.name}</span></a>&nbsp;");
			var tagC = "";
			$.each(k.tagString.split(" "), function(k,v) {
				tagC += $.processTemplateToText(tpl, {name:this});
			});
			e.find(".item-tags").html(tagC);
			
		}
		that.addMode();
		
	},
	
	_addCallback: function(event) {
		var that = event.inData.that;
		var k = event.inData.knowte;
		location.reload(); // TODO this is so lame
	}
});
$.pluginMaker(ItemForm);

/**
 * A global ajax error handler which shows an alert
 */
$( "body" ).ajaxError(function(e, jqxhr, settings, exception) {
	// prepare error msg
	var resp = $.parseJSON(jqxhr.responseText);
	alert("failed to process AJAX request with url " + settings.url + ":\n " + resp.errors);
});

/**
 * All callback handler for deleted
 */
var uiDeleteCallback = function(event) {
	// event object should have uiElem attribute
	// which represents the UI element we want to fade out on
	// a successful operation
	if (event.status === "success") {
		$(event.inData.uiElem).fadeOut();
	} else {
		alert("Failed to delete item");
	}
};


/******************************************************************************/

/******************************************************************************
 * GLOBAL OBJECTS AND PREFERENCES
 ******************************************************************************/

/**
 * the servlet context path which has to be adjusted for each installation
 */
var servletContextPath = "";

/**
 * instantiate a dictionary
 */
var urlDict = UrlDict.getInstance(servletContextPath);
var templateCache = TemplateCache.getInstance(servletContextPath);
var knowteClient = KnowteClient.init(urlDict);

/******************************************************************************
 * INITIALIZATION
 ******************************************************************************/
$(document).ready(function() {

	$("#onsiteformContainer").itemForm({knowteClient:knowteClient, templateCache:templateCache});
	
	// edit and delete buttons
	$(".item").each(function(i,v) {
		var knowte = {};
		knowte.id = $(this).find("input[name='itemid']").val();
		knowte.title = $(this).find(".item-title-text").html();
		knowte.url = $(this).find(".item-url-text").html();
		knowte.info = $(this).find(".item-content-text").html();
		// tags
		knowte.tagString = "";
		$(this).find(".item-tag").each(function(k,v) {
			knowte.tagString = knowte.tagString + $(this).html() + " ";
		});
		knowte.tagString = knowte.tagString.trim();

		$(this).find(".edit-btn").on("click", {knowte:knowte}, function(event) {
			event.preventDefault();
			k = event.data.knowte;
			$("#onsiteformContainer").itemForm("editMode", k);
		});
		
		$(this).find(".delete-btn").on("click", {nid:knowte.id, title:knowte.title, that:this}, function(event) {
			event.preventDefault();
			var conf = confirm("Delete item with title \"" + event.data.title + "\"?");
			if ( conf ) {
				console.log("deleting item " + event.data.nid);
			}
			knowteClient.del(event.data.nid, uiDeleteCallback, {uiElem:event.data.that});
		});
		
		
	});
	
	// search field auto-complete
	$("#searchfield").autocomplete({
			source: function(req, res) {
					$.getJSON(servletContextPath +"/api/search/basicComplete", { searchString: searchField.val() }, function(data) {
						res($.map(data, function(item) {
							return {
								label : item.title,
								id : item.id
							};
						}));
					});
				},
			minLength: 2
		});
});